#include "inode.h"
#include "debug.h"
#include "string.h"
#include "interrupt.h"
#include "thread.h"
#include "memory.h"
#include "fs.h"
#include "file.h"

struct inode_position {
    bool two_sec; // inode是否跨扇区
    uint32_t sec_lba; // inode所在扇区号
    uint32_t off_size; // inode在扇区内的字节偏移
};

/* 获取Inode所在的扇区和扇区内的偏移量 */
static void inode_locate(struct partition *part, uint32_t inode_no, struct inode_position *inode_pos) {
    // inode_table_lba在硬盘上连续
    ASSERT(inode_no < 4096);
    uint32_t inode_table_lba = part->sb->inode_table_lba;

    uint32_t inode_size = sizeof(struct inode);
    uint32_t off_size = inode_no * inode_size; // 字节偏移量，相对inode_table_lba
    uint32_t off_sec = off_size / 512;          // 扇区偏移量
    uint32_t off_size_in_sec = off_size % 512;  // 扇区内的字节偏移量

    /* 判断是否跨扇区 */
    uint32_t left_in_sec = 512 - off_size_in_sec;
    if (left_in_sec < inode_size) {
        // 剩下的空间不够一个inode的那肯定跨
        inode_pos->two_sec = true;
    } else {
        inode_pos->two_sec = false;
    }
    inode_pos->sec_lba = inode_table_lba + off_sec; // inode所在扇区号
    inode_pos->off_size = off_size_in_sec;      // 在扇区内的字节偏移量
}

/* 将inode写入到硬盘分区part */
void inode_sync(struct partition *part, struct inode *inode, void *io_buf) {
    // io_buf 是硬盘io缓冲区
    uint8_t inode_no = inode->i_no;
    struct inode_position inode_pos;
    inode_locate(part, inode_no, &inode_pos);   // 获取inode的硬盘中位置
    ASSERT(inode_pos.sec_lba <= (part->start_lba + part->sec_cnt));

    struct inode pure_inode;    // 用于往硬盘中存入inode信息，不需要inode_tag,i_open_cnts，同时清理write_deny项，置false，防止下一次从硬盘读入inode混乱
    memcpy(&pure_inode, inode, sizeof(struct inode));

    pure_inode.i_open_cnts = 0;
    pure_inode.write_deny = false;
    pure_inode.inode_tag.prev = pure_inode.inode_tag.next = NULL;

    char *inode_buf = (char *)io_buf;
    if (inode_pos.two_sec) {
        // 跨扇区，读入两个扇区，再写入
        ide_read(part->my_disk, inode_pos.sec_lba, inode_buf, 2);
        memcpy((inode_buf + inode_pos.off_size), &pure_inode, sizeof(struct inode));
        
        // 拼接好写入硬盘
        ide_write(part->my_disk, inode_pos.sec_lba, inode_buf, 2);
    } else {
        // 不跨扇区
        ide_read(part->my_disk, inode_pos.sec_lba, inode_buf, 1);
        memcpy((inode_buf + inode_pos.off_size), &pure_inode, sizeof(struct inode));
        ide_write(part->my_disk, inode_pos.sec_lba, inode_buf, 1);
    }
}

/* 根据inode节点号，返回相应的i节点 */
struct inode *inode_open(struct partition *part, uint32_t inode_no) {
    // 先在打开的inode链表中寻找
    struct list_elem *elem = part->open_inodes.head.next; // 第一个
    struct inode *inode_found;
    while (elem != &part->open_inodes.tail) {
        inode_found = elem2entry(struct inode, inode_tag, elem);
        if (inode_found->i_no == inode_no) {
            // 找到
            inode_found->i_open_cnts++;
            return inode_found;
        }
        elem = elem->next;
    }
    // 没找到，从硬盘上找，并读入
    struct inode_position inode_pos;
    inode_locate(part, inode_no, &inode_pos);  // 定位硬盘中位置

    // 需要在内核空间申请inoce，供所有任务共享此inode，临时将pcb->pgdir=NULL
    struct task_struct *cur = running_thread();
    uint32_t *cur_pagedir_bak = cur->pgdir;
    cur->pgdir = NULL;
    inode_found = (struct inode *)sys_malloc(sizeof(struct inode)); // 申请到的是内核段的堆内存
    cur->pgdir = cur_pagedir_bak; // 恢复pgdir
    // inode_found内存分配完成，开始读取硬盘到分配好的内存中去
    char *inode_buf;
    if (inode_pos.two_sec) {
        // 跨扇区
        inode_buf = (char *)sys_malloc(1024); // 申请两个扇区大小的缓冲区
        ide_read(part->my_disk, inode_pos.sec_lba, inode_buf, 2); // 读取两个扇区
    } else {
        // 不跨扇区
        inode_buf = (char *)sys_malloc(512);
        ide_read(part->my_disk, inode_pos.sec_lba, inode_buf, 1);
    }
    memcpy(inode_found, inode_buf + inode_pos.off_size, sizeof(struct inode)); // 缓冲区拷贝到分配的堆内存去
    
    // 将这个inode加入到打开的inodes队列首，方便提前检索，可能马上就要用到
    list_push(&part->open_inodes, &inode_found->inode_tag);
    inode_found->i_open_cnts = 1;

    sys_free(inode_buf); // 释放临时缓冲区
    return inode_found;
}

/* 关闭inode，或减少inode打开数 */
void inode_close(struct inode *inode) {
    // 如果没有进程打开，将此inode去掉，并释放空间
    enum intr_status old_status = intr_disable(); // 关中断
    if (--inode->i_open_cnts == 0) {
        // 没有进程打开
        list_remove(&inode->inode_tag); // 从part->open_inodes中去除
        // 释放空间时，也要从内核空间释放
        struct task_struct *cur = running_thread();
        uint32_t *cur_pagedir_bak = cur->pgdir;
        cur->pgdir = NULL;
        sys_free(inode); // 释放内核段的堆内存
        cur->pgdir = cur_pagedir_bak; // 恢复pgdir
    }
    intr_set_status(old_status); // 恢复中断状态
}

/* 将硬盘分区part上的inode清空 */
void inode_delete(struct partition *part, uint32_t inode_no, void *io_buf)
{
    ASSERT(inode_no < 4096);
    struct inode_position inode_pos;
    inode_locate(part, inode_no, &inode_pos); // inode位置信息会存入inode_pos
    ASSERT(inode_pos.sec_lba <= (part->start_lba + part->sec_cnt));

    char *inode_buf = (char *)io_buf;
    if (inode_pos.two_sec)
    { // inode跨扇区,读入2个扇区
        /* 将原硬盘上的内容先读出来 */
        ide_read(part->my_disk, inode_pos.sec_lba, inode_buf, 2);
        /* 将inode_buf清0 */
        memset((inode_buf + inode_pos.off_size), 0, sizeof(struct inode));
        /* 用清0的内存数据覆盖磁盘 */
        ide_write(part->my_disk, inode_pos.sec_lba, inode_buf, 2);
    }
    else
    { // 未跨扇区,只读入1个扇区就好
        /* 将原硬盘上的内容先读出来 */
        ide_read(part->my_disk, inode_pos.sec_lba, inode_buf, 1);
        /* 将inode_buf清0 */
        memset((inode_buf + inode_pos.off_size), 0, sizeof(struct inode));
        /* 用清0的内存数据覆盖磁盘 */
        ide_write(part->my_disk, inode_pos.sec_lba, inode_buf, 1);
    }
}

/* 回收inode的数据块和inode本身 */
void inode_release(struct partition *part, uint32_t inode_no)
{
    struct inode *inode_to_del = inode_open(part, inode_no);
    ASSERT(inode_to_del->i_no == inode_no);

    /* 1 回收inode占用的所有块 */
    uint8_t block_idx = 0, block_cnt = 12;
    uint32_t block_bitmap_idx;
    uint32_t all_blocks[140] = {0}; // 12个直接块+128个间接块

    /* a 先将前12个直接块存入all_blocks */
    while (block_idx < 12)
    {
        all_blocks[block_idx] = inode_to_del->i_sectors[block_idx];
        block_idx++;
    }

    /* b 如果一级间接块表存在,将其128个间接块读到all_blocks[12~], 并释放一级间接块表所占的扇区 */
    if (inode_to_del->i_sectors[12] != 0)
    {
        ide_read(part->my_disk, inode_to_del->i_sectors[12], all_blocks + 12, 1);
        block_cnt = 140;

        /* 回收一级间接块表占用的扇区 */
        block_bitmap_idx = inode_to_del->i_sectors[12] - part->sb->data_start_lba;
        ASSERT(block_bitmap_idx > 0);
        bitmap_set(&part->block_bitmap, block_bitmap_idx, 0);
        bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);
    }

    /* c inode所有的块地址已经收集到all_blocks中,下面逐个回收 */
    block_idx = 0;
    while (block_idx < block_cnt)
    {
        if (all_blocks[block_idx] != 0)
        {
            block_bitmap_idx = 0;
            block_bitmap_idx = all_blocks[block_idx] - part->sb->data_start_lba;
            ASSERT(block_bitmap_idx > 0);
            bitmap_set(&part->block_bitmap, block_bitmap_idx, 0);
            bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);
        }
        block_idx++;
    }

    /*2 回收该inode所占用的inode */
    bitmap_set(&part->inode_bitmap, inode_no, 0);
    bitmap_sync(cur_part, inode_no, INODE_BITMAP);

    /******     以下inode_delete是调试用的    ******
     * 此函数会在inode_table中将此inode清0,
     * 但实际上是不需要的,inode分配是由inode位图控制的,
     * 硬盘上的数据不需要清0,可以直接覆盖*/
    void *io_buf = sys_malloc(1024);
    inode_delete(part, inode_no, io_buf);
    sys_free(io_buf);
    /***********************************************/

    inode_close(inode_to_del);
}

/* 初始化new_inode */
void inode_init(uint32_t inode_no, struct inode *new_inode) {
    new_inode->i_no = inode_no;
    new_inode->i_size = 0;
    new_inode->i_open_cnts = 0;
    new_inode->write_deny = false;

    uint8_t sec_idx = 0;
    while (sec_idx < 13) {
        // 0~11直接块，12是一级间接块
        // 初始化索引数组i_sector[13]
        new_inode->i_sectors[sec_idx] = 0;
        sec_idx++;
    }
}